Micron Document
NexusPi Git Node

Node / mirrors / microReticulum_Firmware / files / WebSocketServer.h

Displaying Raw • Download

WebSocketServer.h f0aee0671e84b627f90883c0f1fca8f0d4dbc9da (f0aee067) Text, 7.05 KB

T8b949e// Cross-platform RFC 6455 WebSocket server, hand-rolled to sit on top of
T8b949e// stock Arduino `WiFiServer`/`WiFiClient`. Works as-is on ESP32 (Arduino
T8b949e// framework) and on native Portduino — no library shims, no preprocessor
T8b949e// platform forks inside this file.
T8b949e//
T8b949e// Scope: server-only, single client at a time, RFC 6455 message
T8b949e// fragmentation supported, no SSL, payload capped at PAYLOAD_CAP bytes.
T8b949e// That's enough for KISS frames (LoRa MTU is sub-300 bytes) and matches
T8b949e// the firmware's existing single-host model (Remote.h / TCPHostInterface
T8b949e// both reject second connections).
T8b949e//
T8b949e// Reads are done exclusively via `client_.read()` (single byte). This is
T8b949e// deliberate: Portduino's `WiFiClient::available()` does a destructive
T8b949e// 1-byte peek into a private member that `WiFiClient::read(buf,size)`
T8b949e// then ignores, dropping the first byte of every burst. The single-byte
T8b949e// `read()` path consumes the stash correctly, so by staying on it we
T8b949e// avoid the bug on Portduino without a WiFiClient subclass workaround.
T8b949e// Per-byte overhead is fine for KISS data rates.

Tff7b72#Tff7b72ifndef WEBSOCKET_SERVER_H
Tff7b72#Tff7b72define WEBSOCKET_SERVER_H

T8b949e// Two-level gate: ENABLE_WEBSOCKETS is the user-facing build flag set in
T8b949e// platformio.ini; the __has_include guard auto-disables the feature on
T8b949e// platforms whose Arduino framework doesn't ship <WiFi.h> (e.g. nRF52).
T8b949e// This lets us set the flag once in [env:embedded] without breaking
T8b949e// non-WiFi targets.
Tff7b72#Tff7b72if defined(ENABLE_WEBSOCKETS) && __has_include(<WiFi.h>)

Tff7b72#Tff7b72include T8b949e<WiFi.h>
Tff7b72#Tff7b72include T8b949e<cstdint>
Tff7b72#Tff7b72include T8b949e<cstddef>
Tff7b72#Tff7b72if defined(PORTDUINO)
Tff7b72#Tff7b72include T8b949e<netinet/in.h>
Tff7b72#Tff7b72endif

Te6edf3class Te6edf3WebSocketServer Tb4b4b4{
Te6edf3publicTff7b72:
Tff7b72static Tff7b72constexpr Tffa657size_t Te6edf3REQ_BUF_CAP Tff7b72= T79c0ff1024Tb4b4b4; T8b949e// handshake request
Tff7b72static Tff7b72constexpr Tffa657size_t Te6edf3PAYLOAD_CAP Tff7b72= T79c0ff2048Tb4b4b4; T8b949e// per-message accumulator
Tff7b72static Tff7b72constexpr Tffa657size_t Te6edf3CTRL_PAYLOAD_CAP Tff7b72= T79c0ff125Tb4b4b4; T8b949e// RFC 6455 §5.5
Tff7b72static Tff7b72constexpr Tffa657uint16_t Te6edf3CLOSE_NORMAL Tff7b72= T79c0ff1000Tb4b4b4;
Tff7b72static Tff7b72constexpr Tffa657uint16_t Te6edf3CLOSE_PROTO Tff7b72= T79c0ff1002Tb4b4b4;
Tff7b72static Tff7b72constexpr Tffa657uint16_t Te6edf3CLOSE_TOOBIG Tff7b72= T79c0ff1009Tb4b4b4;

T8b949e// bind_public is honored only on native (Portduino). When false, the
T8b949e// listening socket is bound to 127.0.0.1; when true, to 0.0.0.0. On
T8b949e// ESP32 the underlying Arduino WiFiServer always binds 0.0.0.0 (which
T8b949e// is correct — the WS console is reached over the device's WiFi AP)
T8b949e// and this argument is ignored. We default to false because the safe
T8b949e// posture matches the existing kiss_tcp_public convention; on ESP32
T8b949e// the default is effectively true anyway.
Te6edf3explicit Td2a8ffWebSocketServerTb4b4b4(Tffa657uint16_t Te6edf3portTb4b4b4, Tffa657bool Te6edf3bind_public Tff7b72= Tffa657falseTb4b4b4)
Tff7b72: Te6edf3port_Tb4b4b4(Te6edf3portTb4b4b4)
Tb4b4b4, Te6edf3bind_public_Tb4b4b4(Te6edf3bind_publicTb4b4b4)
Tff7b72#Tff7b72if !defined(PORTDUINO)
Tb4b4b4, Te6edf3server_Tb4b4b4(Te6edf3portTb4b4b4)
Tff7b72#Tff7b72endif
Tb4b4b4{Tb4b4b4}

Tff7b72~Te6edf3WebSocketServerTb4b4b4(Tb4b4b4)Tb4b4b4;

Tffa657void Td2a8ffbeginTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffserviceTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// call once per main-loop tick

T8b949e// Close the listening socket and drop any active client. Used by the
T8b949e// native deferred-reboot path so the re-exec'd process can re-bind the
T8b949e// same port without inheriting our listen fd. Safe to call when already
T8b949e// shut down (idempotent).
Tffa657void Td2a8ffshutdownTb4b4b4(Tb4b4b4)Tb4b4b4;

Tffa657bool Td2a8ffconnectedTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3state_ Tff7b72=Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3OPENTb4b4b4; Tb4b4b4}

T8b949e// Send a complete frame (FIN=1, no masking — server side).
T8b949e// Returns false if not currently OPEN or if the write call failed.
Tffa657bool Td2a8ffsend_text Tb4b4b4(Tff7b72const Tffa657charTff7b72* Te6edf3textTb4b4b4)Tb4b4b4;
Tffa657bool Td2a8ffsend_binaryTb4b4b4(Tff7b72const Tffa657uint8_tTff7b72* Te6edf3dataTb4b4b4, Tffa657size_t Te6edf3lenTb4b4b4)Tb4b4b4;

T8b949e// Inbound message hook. Invoked once per complete data message
T8b949e// (after any fragmentation has been reassembled). `is_text`
T8b949e// distinguishes opcode 0x1 (text) vs 0x2 (binary).
Te6edf3using Te6edf3MessageCb Tff7b72= Tffa657void Tb4b4b4(Tff7b72*Tb4b4b4)Tb4b4b4(Tff7b72const Tffa657uint8_tTff7b72* Te6edf3dataTb4b4b4, Tffa657size_t Te6edf3lenTb4b4b4,
Tffa657bool Te6edf3is_textTb4b4b4, Tffa657voidTff7b72* Te6edf3ctxTb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffon_messageTb4b4b4(Te6edf3MessageCb Te6edf3cbTb4b4b4, Tffa657voidTff7b72* Te6edf3ctx Tff7b72= Tffa657nullptrTb4b4b4) Tb4b4b4{
Te6edf3on_message_ Tff7b72= Te6edf3cbTb4b4b4;
Te6edf3on_message_ctx_ Tff7b72= Te6edf3ctxTb4b4b4;
Tb4b4b4}

Te6edf3privateTff7b72:
Tff7b72enum Te6edf3class Te6edf3State Tff7b72: Tffa657uint8_t Tb4b4b4{
Te6edf3WAITINGTb4b4b4, T8b949e// no client; accept loop active
Te6edf3HANDSHAKINGTb4b4b4, T8b949e// client attached; parsing HTTP upgrade
Te6edf3OPENTb4b4b4, T8b949e// handshake complete; framing live
Te6edf3CLOSINGTb4b4b4, T8b949e// close handshake in progress; teardown next tick
Tb4b4b4}Tb4b4b4;

T8b949e// Frame-parser cursor. We advance one byte at a time, so the state
T8b949e// tells us what the next byte is.
Tff7b72enum Te6edf3class Te6edf3RxPhase Tff7b72: Tffa657uint8_t Tb4b4b4{
Te6edf3HEAD_0Tb4b4b4, T8b949e// FIN + reserved + opcode
Te6edf3HEAD_1Tb4b4b4, T8b949e// MASK + 7-bit length
Te6edf3EXT_LENTb4b4b4, T8b949e// 2 or 8 bytes of extended length
Te6edf3MASKTb4b4b4, T8b949e// 4 bytes of mask key
Te6edf3PAYLOADTb4b4b4, T8b949e// payload bytes (unmasked on the fly)
Tb4b4b4}Tb4b4b4;

Tffa657void Td2a8ffdrive_acceptTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffdrive_handshakeTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffdrive_frameTb4b4b4(Tb4b4b4)Tb4b4b4;

Tffa657bool Td2a8fffinish_handshakeTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// parse req_buf_, write 101 or 400
Tffa657void Td2a8ffdispatch_frameTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// dispatch the just-completed frame
Tffa657bool Td2a8ffsend_frameTb4b4b4(Tffa657uint8_t Te6edf3opcodeTb4b4b4, Tff7b72const Tffa657uint8_tTff7b72* Te6edf3dataTb4b4b4, Tffa657size_t Te6edf3lenTb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffsend_closeTb4b4b4(Tffa657uint16_t Te6edf3codeTb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffteardownTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffreset_frame_rxTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// frame-scoped state, NOT message-scoped

T8b949e// The listening socket diverges by platform: on native (Portduino) we
T8b949e// own a raw POSIX fd so we can choose the bind address; on ESP32 we
T8b949e// use the stock Arduino WiFiServer (which always binds 0.0.0.0). Both
T8b949e// paths still feed an accepted connection into a WiFiClient member,
T8b949e// so the rest of the protocol code is platform-agnostic.
Tffa657uint16_t Te6edf3port_Tb4b4b4;
Tffa657bool Te6edf3bind_public_Tb4b4b4;
Tff7b72#Tff7b72if defined(PORTDUINO)
Tffa657int Te6edf3listen_fd_ Tff7b72= T79c0ff-1Tb4b4b4;
Tff7b72#Tff7b72else
Te6edf3WiFiServer Te6edf3server_Tb4b4b4;
Tff7b72#Tff7b72endif
Te6edf3WiFiClient Te6edf3client_Tb4b4b4;
Tff7b72#Tff7b72if defined(PORTDUINO)
T8b949e// Stashed peer address from accept(), used by connect/disconnect logs.
T8b949e// ESP32 reads the equivalent via client_.remoteIP() directly.
Te6edf3sockaddr_in Te6edf3client_addr_ Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4;
Tff7b72#Tff7b72endif
Te6edf3State Te6edf3state_ Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3WAITINGTb4b4b4;
Te6edf3MessageCb Te6edf3on_message_ Tff7b72= Tffa657nullptrTb4b4b4;
Tffa657voidTff7b72* Te6edf3on_message_ctx_Tff7b72= Tffa657nullptrTb4b4b4;

T8b949e// Handshake buffer
Tffa657uint8_t Te6edf3req_buf_Tb4b4b4[Te6edf3REQ_BUF_CAPTb4b4b4]Tb4b4b4;
Tffa657size_t Te6edf3req_len_ Tff7b72= T79c0ff0Tb4b4b4;

T8b949e// ---- Per-frame state (reset between frames) ----
Te6edf3RxPhase Te6edf3rx_phase_ Tff7b72= Te6edf3RxPhaseTff7b72:Tff7b72:Te6edf3HEAD_0Tb4b4b4;
Tffa657uint8_t Te6edf3rx_opcode_ Tff7b72= T79c0ff0Tb4b4b4; T8b949e// raw opcode of the in-flight frame
Tffa657bool Te6edf3rx_fin_ Tff7b72= Tffa657falseTb4b4b4;
Tffa657bool Te6edf3rx_is_ctrl_ Tff7b72= Tffa657falseTb4b4b4;
Tffa657bool Te6edf3rx_masked_ Tff7b72= Tffa657falseTb4b4b4;
Tffa657uint8_t Te6edf3rx_ext_remaining_ Tff7b72= T79c0ff0Tb4b4b4;
Tffa657uint8_t Te6edf3rx_mask_pos_ Tff7b72= T79c0ff0Tb4b4b4;
Tffa657uint8_t Te6edf3rx_mask_Tb4b4b4[T79c0ff4Tb4b4b4] Tff7b72= Tb4b4b4{T79c0ff0Tb4b4b4}Tb4b4b4;
Tffa657uint32_t Te6edf3rx_payload_pos_ Tff7b72= T79c0ff0Tb4b4b4; T8b949e// within current frame's payload
Tffa657uint32_t Te6edf3rx_payload_total_ Tff7b72= T79c0ff0Tb4b4b4;

T8b949e// ---- Per-message state (persists across fragments) ----
T8b949e// 0 = no message in progress; 0x1/0x2 = TEXT/BIN message being built.
Tffa657uint8_t Te6edf3msg_opcode_ Tff7b72= T79c0ff0Tb4b4b4;
Tffa657uint32_t Te6edf3msg_len_ Tff7b72= T79c0ff0Tb4b4b4; T8b949e// bytes accumulated so far
Tffa657uint8_t Te6edf3payload_Tb4b4b4[Te6edf3PAYLOAD_CAPTb4b4b4]Tb4b4b4;

T8b949e// Control-frame payload buffer. Per RFC 6455 §5.5, control frames
T8b949e// may not be fragmented and carry ≤125-byte payloads. They can be
T8b949e// interleaved with data fragments, so they need their own buffer.
Tffa657uint8_t Te6edf3ctrl_buf_Tb4b4b4[Te6edf3CTRL_PAYLOAD_CAPTb4b4b4]Tb4b4b4;
Tb4b4b4}Tb4b4b4;

Tff7b72#Tff7b72endif T8b949e// ENABLE_WEBSOCKETS && __has_include(<WiFi.h>)
Tff7b72#Tff7b72endif T8b949e// WEBSOCKET_SERVER_H

Served by rngit 1.5.2 - Generated in 0.02s